
#pragma once

#include "BVHRenderer.h"
#include "BVH.h"
#include "boost/thread.hpp"
#include "Prerequisites.h"

class BVHRendererThreaded : public BVHRenderer
{
private:
	struct Worker
	{
		Worker(int _pixelIndexStart, int _pixelIndexEnd, BVHRendererThreaded *_renderer)
			: pixelIndexStart(_pixelIndexStart), pixelIndexEnd(_pixelIndexEnd), renderer(_renderer) {}
		int pixelIndexStart, pixelIndexEnd;
		BVHRendererThreaded *renderer;
		void operator()()
		{
			Scene &scene = *renderer->mCurrScene;
			const Camera &camera = *renderer->mCurrCamera;
			int p = pixelIndexStart;
			int x = pixelIndexStart % camera.getProjection().getWidth();
			for (int y = pixelIndexStart / camera.getProjection().getWidth(); y < camera.getProjection().getHeight(); y++)
			{
				int yInv = camera.getProjection().getHeight() - y;
				for (; x < camera.getProjection().getWidth(); x++)
				{
					(*renderer->mCurrRenderedImage)[p] = renderer->samplePixel(camera, scene, x, yInv);
					(*renderer->mCurrRenderedImage)[p].saturate();
					p++;
					if (p == pixelIndexEnd) return;
				}
				x = 0;
			}
		}
	};

	Scene *mCurrScene;
	const Camera *mCurrCamera;
	std::vector<Ogre::ColourValue> *mCurrRenderedImage;
	int mNumThreadsTarget;

public:
	~BVHRendererThreaded() {}
	BVHRendererThreaded() : mNumThreadsTarget(80) {}

	/// Sets how many threads shall be used approximatively.
	void setNumThreadsTarget(int numThreads)
	{
		mNumThreadsTarget = numThreads;
	}

	void renderScene(std::vector<Ogre::ColourValue> &renderedImage, Scene &scene, const Camera &camera)
	{
		mCurrRenderedImage = &renderedImage;
		mCurrScene = &scene;
		mCurrCamera = &camera;
		mRayCounter = 0;
		mShadowRayCounter = 0;
		if (renderedImage.size() != camera.getProjection().getWidth() * camera.getProjection().getHeight())
		{
			renderedImage.clear();
			renderedImage.resize(camera.getProjection().getWidth() * camera.getProjection().getHeight());
		}

		Profiler::Timestamp timeStamp = Profiler::timestamp();
		scene.applyViewTransform(camera.getViewMatrix());
		Profiler::printJobDuration("Scene preprocessing", timeStamp);

		//construct the BVH
		timeStamp = Profiler::timestamp();

		std::vector<Surface*> surfaces;

		for (auto i = scene.spheres.begin(); i != scene.spheres.end(); i++) surfaces.push_back(*i);

		for (auto iMesh = scene.meshes.begin(); iMesh != scene.meshes.end(); ++iMesh)
		{
			for (auto iTri = (*iMesh)->triangleSurfaces.begin(); iTri != (*iMesh)->triangleSurfaces.end(); iTri++)
				surfaces.push_back(&(*iTri));
		}
		mRootNode = new BVHNodeThreaded<DefaultBV, Surface>(surfaces, 0, surfaces.size(), 0, static_cast<int>(std::log((double)mNumThreadsTarget)/std::log(2.0)));
		Profiler::printJobDuration("BVH creation", timeStamp);
		std::cout << "Tree height max: " << mRootNode->getHeight() << std::endl;
		std::cout << "Tree height avg: " << mRootNode->getHeightAvg() << std::endl;

		timeStamp = Profiler::timestamp();
		int numPixels = camera.getProjection().getWidth() * camera.getProjection().getHeight();
		int pixelsPerThread = numPixels / mNumThreadsTarget;
		boost::thread_group threads;
		for (int startPixel = 0; startPixel < numPixels; startPixel += pixelsPerThread)
		{
			Worker worker(startPixel, min(numPixels, startPixel + pixelsPerThread), this);
			threads.create_thread(worker);
		}
		threads.join_all();
		Profiler::printJobDuration("Ray tracing", timeStamp);

		std::cout << mRayCounter << " rays were casted." << std::endl;
		std::cout << mShadowRayCounter << " shadow rays were casted." << std::endl;

		delete mRootNode;
		mRootNode = nullptr;
	}
};